{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "b7cf53b5",
   "metadata": {
    "height": 30
   },
   "source": [
    "# 第六章 与任何大语言模型聊天! 💬"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "98f7893c",
   "metadata": {
    "height": 30
   },
   "source": [
    "在最后这一课中，我们将构建一个与开源 LLM 聊天的应用程序。我们将使用最好的开源模型之一，Falcon 40B。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d8d0ff1d",
   "metadata": {
    "height": 30
   },
   "source": [
    "我们将使用开源 LLM 构建一个聊天机器人应用程序。你可能已经用 ChatGPT 聊过天了，但运行它不仅成本高昂，交互的模式还较为死板。自定义 LLM 可以在本地运行，在自己的数据中进行微调，或者在云上运行，成本更低。在本课中，我们将使用运行 \"falcon-40B-Instruct \"的推理端点。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "062996ff",
   "metadata": {
    "height": 30
   },
   "source": [
    "使用文本生成推理库在本地运行很方便。当然，也可以使用 Gradio 创建接口。Gradio 是基于 API 的 LLM，因此不仅支持开源的当然，你也可以使用 Gradio 为基于 API 的 LLM 创建接口，所以不仅仅是开源 LLM。但在本课程中，我们将重点介绍开源 LLM Falcon 40B。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f8ce5955",
   "metadata": {
    "height": 30
   },
   "source": [
    "加载 HF API 密钥和相关 Python 库"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "0fa6fa00-6bd1-4839-bcaf-8bae9267ee79",
   "metadata": {
    "height": 199
   },
   "outputs": [],
   "source": [
    "# 导入必要的库\n",
    "import os                # 用于操作系统相关的操作，例如读取环境变量\n",
    "import io                # 用于处理流式数据（例如文件流）\n",
    "import IPython.display   # 用于在IPython环境中显示数据，例如图片\n",
    "from PIL import Image    # 用于处理图像数据\n",
    "import base64            # 用于处理base64编码，通常用于编码图像数据\n",
    "import requests          # 用于进行HTTP请求，例如GET和POST请求\n",
    "\n",
    "# 设置请求的默认超时时间为60秒\n",
    "requests.adapters.DEFAULT_TIMEOUT = 60\n",
    "\n",
    "# 导入dotenv库的函数\n",
    "# dotenv允许您从.env文件中读取环境变量\n",
    "# 这在开发时特别有用，可以避免将敏感信息（如API密钥）硬编码到代码中\n",
    "from dotenv import load_dotenv, find_dotenv\n",
    "\n",
    "# 寻找.env文件并加载它的内容\n",
    "# 这允许您使用os.environ来读取在.env文件中设置的环境变量\n",
    "_ = load_dotenv(find_dotenv())\n",
    "\n",
    "# 从环境变量中读取'HF_API_KEY'并将其存储在hf_api_key变量中\n",
    "hf_api_key = os.environ['HF_API_KEY']"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fa4f627a",
   "metadata": {
    "height": 30
   },
   "source": [
    "我们在这里设置token和辅助函数。你可以看到，在这里我们使用了不同的库。我们使用的是文本生成库，这是一个用于处理开源 LLM 的精简库，可以让你同时加载 API（就像我们在这里做的一样），也可以在本地运行你自己的 LLM。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "095da8fe-24aa-4dc7-8e08-aa2f949ae21f",
   "metadata": {
    "height": 131
   },
   "outputs": [],
   "source": [
    "# 助手函数\n",
    "\n",
    "# 导入必要的库\n",
    "import requests        # 用于进行HTTP请求\n",
    "import json            # 用于处理JSON数据\n",
    "\n",
    "# 导入自定义模块中的Client类\n",
    "# 假设这个类是用于与FalcomLM-instruct端点通信的\n",
    "from text_generation import Client\n",
    "\n",
    "# 使用os.environ从环境变量中获取'HF_API_FALCOM_BASE'的值，这应该是FalcomLM的基础URL\n",
    "# 使用hf_api_key作为身份验证的一部分创建一个客户端实例\n",
    "# 设置请求超时时间为120秒\n",
    "client = Client(os.environ['HF_API_FALCOM_BASE'], \n",
    "                headers={\"Authorization\": f\"Basic {hf_api_key}\"}, \n",
    "                timeout=120)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7d034a95",
   "metadata": {
    "height": 30
   },
   "source": [
    "## 建立一个应用程序，与任何LLM聊天！"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4c335e24",
   "metadata": {
    "height": 30
   },
   "source": [
    "这里我们将使用一个 [推理端点](https://huggingface.co/inference-endpoints) 来调用 `falcon-40b-instruct` ,  其在[🤗 开源LLM榜单](https://huggingface.co/spaces/HuggingFaceH4/open_llm_leaderboard)上名列前茅。\n",
    "\n",
    "如果要本地调用它, 可以使用 [Transformers 库](https://huggingface.co/docs/transformers/index) 或者 [文本生成推理库](https://github.com/huggingface/text-generation-inference) "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "d14f6261",
   "metadata": {
    "height": 47
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'\\n数学是被发现的，因为它是自然界的基本规律和模式。人类只是发现了这些规律和模式，并将它们用符号和公式表达出来。'"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 设置一个中文提示，该提示将被送到模型中用于生成文本\n",
    "prompt_chinese = \"数学是被发明还是被发现的？\"\n",
    "\n",
    "# 调用client的generate方法\n",
    "# 使用先前设置的中文提示 prompt_chinese \n",
    "# max_new_tokens 参数用于限制生成的文本长度\n",
    "# 然后从返回的结果中获取生成的文本\n",
    "client.generate(prompt_chinese, max_new_tokens=256).generated_text"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "id": "a7065860-3c0b-490d-9e7c-22e5b79fc004",
   "metadata": {
    "height": 47
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'\\nMath has been both invented and discovered. It is a human invention in the sense that it is a system of rules and concepts that we have created to help us understand the world around us. However, it is also a discovery in the sense that it is a fundamental aspect of the universe that we have uncovered through our observations and experiments.'"
      ]
     },
     "execution_count": 33,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "prompt = \"Has math been invented or discovered?\"\n",
    "client.generate(prompt, max_new_tokens=256).generated_text"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e85eeeba",
   "metadata": {
    "height": 30
   },
   "source": [
    "在第 2 课中，我们使用了一个非常简单的 Gradio 界面，它有一个文本框输入和一个输出。在这里，我们也可以用类似的方式与 LLM 聊天。再次复制我们的prompt。在这里，我们可以决定需要多少token。这就是非常简单地向 LLM 提问的方法。但我们还是不能聊天，因为如果你再问一个后续问题，它就无法理解或保留上下文。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0dcb659e-b71b-46da-b9d2-6ee62498995f",
   "metadata": {
    "height": 182
   },
   "outputs": [],
   "source": [
    "# 回到第 2 课，时间过得真快！\n",
    "# 导入所需的库\n",
    "import gradio as gr  # 用于创建Web界面\n",
    "import os  # 用于与操作系统交互，如读取环境变量\n",
    "\n",
    "# 定义一个函数来根据输入生成文本\n",
    "def generate(input, slider):\n",
    "    # 使用预定义的client对象的generate方法，从输入生成文本\n",
    "    # slider的值限制生成的token的数量\n",
    "    output = client.generate(input, max_new_tokens=slider).generated_text\n",
    "    return output  # 返回生成的文本\n",
    "\n",
    "# 创建一个Web界面\n",
    "# 输入：一个文本框和一个滑块\n",
    "# 输出：一个文本框显示生成的文本\n",
    "demo = gr.Interface(\n",
    "    fn=generate, \n",
    "    inputs=[\n",
    "        gr.Textbox(label=\"Prompt\"),  # 文本输入框\n",
    "        gr.Slider(label=\"Max new tokens\", value=20,  maximum=1024, minimum=1)  # 滑块用于选择生成的token的最大数量\n",
    "    ], \n",
    "    outputs=[gr.Textbox(label=\"Completion\")]  # 显示生成文本的文本框\n",
    ")\n",
    "\n",
    "# 关闭可能已经启动的任何先前的gradio实例\n",
    "gr.close_all()\n",
    "\n",
    "# 启动Web界面\n",
    "# 使用环境变量PORT1作为服务器的端口号\n",
    "demo.launch(share=True, server_port=int(os.environ['PORT1']))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "79e5b428",
   "metadata": {
    "height": 30
   },
   "source": [
    "因此，基本上我们要做的是，向模型发送我们之前的问题、它自己的回答以及后续问题。但建立所有这些都有点麻烦。这就是 Gradio 聊天机器人组件的作用所在，因为它允许我们简化向模型发送对话历史记录的过程。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bdc48b74",
   "metadata": {
    "height": 30
   },
   "source": [
    "因此，我们要解决这个问题。为此，我们将引入一个新的 Gradio 组件--Gradio Chatbot。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8d1943fd-213a-48bb-966e-e84b9ae255b1",
   "metadata": {},
   "source": [
    "![math](images/ch06_math.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "33547d43",
   "metadata": {
    "height": 30
   },
   "source": [
    "## 使用 `gr.Chatbot()` 来助力!"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "83360647",
   "metadata": {
    "height": 30
   },
   "source": [
    "让我们开始使用 Gradio Chatbot 组件。这里实例化了一个带有文本框提示和提交按钮的 Gradle ChatBot 组件，是一个非常简单的用户界面。但我们现在还不是在和 LLM 聊天。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0c70dcae",
   "metadata": {
    "height": 30
   },
   "source": [
    "只需随机选择三个预设回复，然后将我的信息和机器人的信息添加到聊天记录中。所以在这里，你可以看到我可以说任何话，它基本上会随机查看这三个回复。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "43beebb7-40a6-4af5-a701-882821b6ed36",
   "metadata": {
    "height": 369
   },
   "outputs": [],
   "source": [
    "# 导入random库，用于随机选择消息\n",
    "import random\n",
    "import gradio as gr  # 用于创建Web界面\n",
    "import os  # 用于与操作系统交互，如读取环境变量\n",
    "\n",
    "# 定义机器人的响应函数\n",
    "def respond(message, chat_history):\n",
    "    # 随机选择一个中文回复\n",
    "    bot_message_chinese = random.choice([\"告诉我更多信息\", \n",
    "                                         \"酷，但我不感兴趣\", \n",
    "                                         \"嗯，那好吧\"])\n",
    "    \n",
    "    # 随机选择一个英文回复 (此回复未在界面中使用)\n",
    "    bot_message = random.choice([\"Tell me more about it\", \n",
    "                                 \"Cool, but I'm not interested\", \n",
    "                                 \"Hmmmm, ok then\"]) \n",
    "    \n",
    "    # 将用户的消息和机器人的中文回复添加到聊天历史记录中\n",
    "    chat_history.append((message, bot_message_chinese))\n",
    "    \n",
    "    # 返回一个空字符串和更新后的聊天历史记录\n",
    "    return \"\", chat_history\n",
    "\n",
    "# 使用gr.Blocks()上下文管理器来定义用户界面\n",
    "with gr.Blocks() as demo:\n",
    "    # 定义一个聊天机器人窗口，高度为240\n",
    "    chatbot = gr.Chatbot(height=240) \n",
    "    # 定义一个用户输入消息的文本框\n",
    "    msg = gr.Textbox(label=\"Prompt\")\n",
    "    # 定义一个提交按钮\n",
    "    btn = gr.Button(\"Submit\")\n",
    "    # 定义一个清除文本框和聊天窗口内容的按钮\n",
    "    clear = gr.ClearButton(components=[msg, chatbot], value=\"Clear console\")\n",
    "\n",
    "    # 当用户点击提交按钮时，调用响应函数\n",
    "    btn.click(respond, inputs=[msg, chatbot], outputs=[msg, chatbot])\n",
    "    # 当用户在文本框中按下Enter键时，也调用响应函数\n",
    "    msg.submit(respond, inputs=[msg, chatbot], outputs=[msg, chatbot])\n",
    "\n",
    "# 关闭可能已经启动的任何先前的gradio实例\n",
    "gr.close_all()\n",
    "\n",
    "# 启动Web界面\n",
    "# 使用环境变量PORT2作为服务器的端口号\n",
    "demo.launch(share=True, server_port=int(os.environ['PORT2']))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0179df18-bad5-430d-a91b-1fb0c972d3ca",
   "metadata": {},
   "source": [
    "![math_with_template](images/ch06_math_with_template.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3d932fde-da5e-47f1-959b-86b053bb9a42",
   "metadata": {},
   "source": [
    "我们必须格式化聊天prompt。此处正在定义这个格式化聊天prompt函数。\n",
    "在这里，我们要做的就是使其包含聊天历史记录，这样 LLM 就能知道上下文。\n",
    "但这还不够。我们还需要告诉它，哪些信息来自用户，哪些信息来自 LLM 本身，也就是我们正在调用的助手。\n",
    "因此，我们设置了格式聊天prompt功能，在聊天记录的每一轮中，都包含一条用户信息和一条助手信息，以便我们的模型能准确回答后续问题。\n",
    "现在，我们要将格式化的prompt传递给我们的 API。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "55bae99d-7a63-4a40-bab7-de7d10b8ab1b",
   "metadata": {
    "height": 471
   },
   "outputs": [],
   "source": [
    "# 定义一个函数，用于格式化聊天提示。\n",
    "def format_chat_prompt(message, chat_history):\n",
    "    # 初始化一个空字符串，用于存放格式化后的聊天提示。\n",
    "    prompt = \"\"\n",
    "    # 遍历聊天历史记录。\n",
    "    for turn in chat_history:\n",
    "        # 从聊天记录中提取用户和机器人的消息。\n",
    "        user_message, bot_message = turn\n",
    "        # 更新提示，加入用户和机器人的消息。\n",
    "        prompt = f\"{prompt}\\nUser: {user_message}\\nAssistant: {bot_message}\"\n",
    "    # 将当前的用户消息也加入到提示中，并预留一个位置给机器人的回复。\n",
    "    prompt = f\"{prompt}\\nUser: {message}\\nAssistant:\"\n",
    "    # 返回格式化后的提示。\n",
    "    return prompt\n",
    "\n",
    "# 定义一个函数，用于生成机器人的回复。\n",
    "def respond(message, chat_history):\n",
    "    # 调用上面的函数，将用户的消息和聊天历史记录格式化为一个提示。\n",
    "    formatted_prompt = format_chat_prompt(message, chat_history)\n",
    "    # 使用client对象的generate方法生成机器人的回复（注意：client对象在此代码中并未定义）。\n",
    "    bot_message = client.generate(formatted_prompt,\n",
    "                                  max_new_tokens=1024,\n",
    "                                  stop_sequences=[\"\\nUser:\", \"\"]).generated_text\n",
    "    # 将用户的消息和机器人的回复加入到聊天历史记录中。\n",
    "    chat_history.append((message, bot_message))\n",
    "    # 返回一个空字符串和更新后的聊天历史记录（这里的空字符串可以替换为真正的机器人回复，如果需要显示在界面上）。\n",
    "    return \"\", chat_history\n",
    "\n",
    "# 下面的代码是设置Gradio界面的部分。\n",
    "\n",
    "# 使用Gradio的Blocks功能定义一个代码块。\n",
    "with gr.Blocks() as demo:\n",
    "    # 创建一个Gradio聊天机器人组件，设置其高度为240。\n",
    "    chatbot = gr.Chatbot(height=240) \n",
    "    # 创建一个文本框组件，用于输入提示。\n",
    "    msg = gr.Textbox(label=\"Prompt\")\n",
    "    # 创建一个提交按钮。\n",
    "    btn = gr.Button(\"Submit\")\n",
    "    # 创建一个清除按钮，用于清除文本框和聊天机器人组件的内容。\n",
    "    clear = gr.ClearButton(components=[msg, chatbot], value=\"Clear console\")\n",
    "\n",
    "    # 设置按钮的点击事件。当点击时，调用上面定义的respond函数，并传入用户的消息和聊天历史记录，然后更新文本框和聊天机器人组件。\n",
    "    btn.click(respond, inputs=[msg, chatbot], outputs=[msg, chatbot])\n",
    "    # 设置文本框的提交事件（即按下Enter键时）。功能与上面的按钮点击事件相同。\n",
    "    msg.submit(respond, inputs=[msg, chatbot], outputs=[msg, chatbot]) \n",
    "\n",
    "# 关闭所有已经存在的Gradio实例。\n",
    "gr.close_all()\n",
    "# 启动新的Gradio应用，设置分享功能为True，并使用环境变量PORT3指定服务器端口。\n",
    "demo.launch(share=True, server_port=int(os.environ['PORT3']))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5e45875a-8ca4-4124-982e-f2ee6c6e597a",
   "metadata": {},
   "source": [
    "![animal](images/ch06_animal.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "022bb649-868d-453d-95e4-fef9cb1feadd",
   "metadata": {},
   "source": [
    "![animal_in_context](images/ch06_animal_in_context.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4dae2ad9",
   "metadata": {
    "height": 30
   },
   "source": [
    "现在，我们的聊天机器人应该可以回答后续问题了。\n",
    "我们可以看到，我们向它发送了上下文。我们向它发送了信息，然后要求它完成。一旦我们进入另一个迭代循环，我们就会向它发送我们的整个上下文，然后要求它完成。这很酷。但是，如果我们一直这样迭代下去，那么模型在一次对话中所能接受的信息量就会达到极限，因为我们总是给它越来越多的之前对话的内容。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "250fcfff",
   "metadata": {
    "height": 46
   },
   "source": [
    "为了让模型发挥最大作用，我们可以在这里将最大token数`max_new_tokens`设置为 1024、 这是我们在 API 中运行的硬件条件下，该模型所能接受的最大值。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a14b9d8d",
   "metadata": {
    "height": 64
   },
   "source": [
    "可以尝试以下prompt：\n",
    "1. 哪些动物生活在热带草原？\n",
    "2. 这之中哪种动物最强壮？"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2ded928f",
   "metadata": {
    "height": 30
   },
   "source": [
    "这里，我们创建了一个简单但功能强大的用户界面，用于与LLM聊天。如果需要进一步Gradio 所能提供的最佳功能，我们可以创建一个包含更多功能的用户界面。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "23b69830",
   "metadata": {
    "height": 30
   },
   "source": [
    "### 添加其他高级功能"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "09873dfd-5b6c-41d6-9479-12e8c8894295",
   "metadata": {
    "height": 828
   },
   "outputs": [],
   "source": [
    "# 定义一个函数，用于格式化聊天提示。\n",
    "def format_chat_prompt(message, chat_history, instruction):\n",
    "    # 初始化提示，加入系统指令。\n",
    "    prompt = f\"System:{instruction}\"\n",
    "    # 遍历聊天历史记录。\n",
    "    for turn in chat_history:\n",
    "        # 从聊天记录中提取用户和机器人的消息。\n",
    "        user_message, bot_message = turn\n",
    "        # 更新提示，加入用户和机器人的消息。\n",
    "        prompt = f\"{prompt}\\nUser: {user_message}\\nAssistant: {bot_message}\"\n",
    "    # 将当前的用户消息也加入到提示中，并预留一个位置给机器人的回复。\n",
    "    prompt = f\"{prompt}\\nUser: {message}\\nAssistant:\"\n",
    "    # 返回格式化后的提示。\n",
    "    return prompt\n",
    "\n",
    "# 定义一个函数，用于生成机器人的回复。\n",
    "def respond(message, chat_history, instruction, temperature=0.7):\n",
    "    # 调用上面的函数，将用户的消息、聊天历史记录和系统指令格式化为一个提示。\n",
    "    prompt = format_chat_prompt(message, chat_history, instruction)\n",
    "    # 更新聊天历史记录，先加入用户的消息（机器人的回复部分先为空）。\n",
    "    chat_history = chat_history + [[message, \"\"]]\n",
    "    # 使用client对象的generate_stream方法生成机器人的回复（注意：client对象在此代码中并未定义）。\n",
    "    stream = client.generate_stream(prompt,\n",
    "                                    max_new_tokens=1024,\n",
    "                                    stop_sequences=[\"\\nUser:\", \"\"], \n",
    "                                    temperature=temperature)  # 设置生成回复的温度，决定回复的随机性。\n",
    "    acc_text = \"\"\n",
    "    # 使用流式处理获取机器人的回复。\n",
    "    for idx, response in enumerate(stream):\n",
    "        text_token = response.token.text\n",
    "\n",
    "        # 如果有任何详情信息，直接返回。\n",
    "        if response.details:\n",
    "            return\n",
    "\n",
    "        # 如果是第一个令牌并且它以空格开始，则去除该空格。\n",
    "        if idx == 0 and text_token.startswith(\" \"):\n",
    "            text_token = text_token[1:]\n",
    "\n",
    "        # 累积生成的文本。\n",
    "        acc_text += text_token\n",
    "        # 更新最后一轮的聊天记录。\n",
    "        last_turn = list(chat_history.pop(-1))\n",
    "        last_turn[-1] += acc_text\n",
    "        chat_history = chat_history + [last_turn]\n",
    "        yield \"\", chat_history\n",
    "        acc_text = \"\"\n",
    "\n",
    "# 设置Gradio界面部分。\n",
    "with gr.Blocks() as demo:\n",
    "    # 创建一个Gradio聊天机器人组件，并设置其高度。\n",
    "    chatbot = gr.Chatbot(height=240)\n",
    "    # 创建一个文本框组件，用于输入提示。\n",
    "    msg = gr.Textbox(label=\"Prompt\")\n",
    "    # 创建一个可折叠组件，用于显示高级选项。\n",
    "    with gr.Accordion(label=\"Advanced options\", open=False):\n",
    "        # 在可折叠组件内创建一个文本框，用于输入系统消息。\n",
    "        system = gr.Textbox(label=\"System message\", lines=2, value=\"一段用户和基于大语言模型的法律助手的对话. 助手会给出真实且有帮助的回答.\")\n",
    "        # 创建一个滑块，用于调整回复的温度。\n",
    "        temperature = gr.Slider(label=\"temperature\", minimum=0.1, maximum=1, value=0.7, step=0.1)\n",
    "    # 创建一个提交按钮。\n",
    "    btn = gr.Button(\"Submit\")\n",
    "    # 创建一个清除按钮，用于清除文本框和聊天机器人组件的内容。\n",
    "    clear = gr.ClearButton(components=[msg, chatbot], value=\"Clear console\")\n",
    "\n",
    "    # 设置按钮的点击事件。当点击时，调用上面定义的respond函数，并传入用户的消息、聊天历史记录和系统消息，然后更新文本框和聊天机器人组件。\n",
    "    btn.click(respond, inputs=[msg, chatbot, system], outputs=[msg, chatbot])\n",
    "    # 设置文本框的提交事件（即按下Enter键时）。功能与上面的按钮点击事件相同。\n",
    "    msg.submit(respond, inputs=[msg, chatbot, system], outputs=[msg, chatbot])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2bf44cb6-c55c-45eb-8aca-c60f08aa1e0f",
   "metadata": {},
   "source": [
    "![law_1](images/ch06_law_1.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "70256e38-a97f-4375-9083-720571c6cc2c",
   "metadata": {},
   "source": [
    "![law_2](images/ch06_law_2.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d3e821c9-f568-4cf5-92b9-dee9f19a45d8",
   "metadata": {},
   "source": [
    "![law_3](images/ch06_law_3.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "776a928b-1620-4bb0-ab80-0c78ad47b96d",
   "metadata": {},
   "source": [
    "在这里，我们有高级选项，包括系统消息，它可以设置 LLM 与你聊天的模式。\n",
    "因此，在系统消息中，你可以说，例如，你是一个乐于助人的助手，或者你可以给它一个特定的语气，一个特定的语调，\n",
    "你希望它更有趣一点，更严肃一点，你真的可以反复调试系统消息提示，看看它对你的消息有什么影响。\n",
    "\n",
    "有些人甚至会想给 LLM 一个角色，比如你是一个提供法律建议的律师，或者你是一个提供医疗建议的医生，\n",
    "但要注意的是，众所周知，LLM 会以一种听起来很真实的方式提供虚假信息。\n",
    "因此，尽管使用Falcon 40B 进行实验和探索会很有趣，但在现实世界的应用场景中，必须为此类使用案例制定进一步的保障措施。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "99e0e822-ec15-4cfe-9bfe-e90250fbd85e",
   "metadata": {},
   "source": [
    "还有其他高级参数，比如这里的温度。\n",
    "温度基本上就是你希望模型的变化程度。因此，如果将温度设为零，模型就会倾向于始终对相同的输入做出相同的反应。\n",
    "所以同样的问题，同样的答案。温度越高，信息的变化就越多。但如果温度过高，它就会开始给出无意义的答案。\n",
    "因此，0.7 是一个不错的默认参数，但我们鼓励你多做尝试。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "183d7964",
   "metadata": {
    "height": 30
   },
   "source": [
    "除此之外，这个用户界面还能让我们进行流式传输回复。\n",
    "它逐个token发送，我们可以看到它实时完成。因此，我们不需要等到整个答案都准备好了。在这里，我们可以看到它是如何完成的。如果你不理解这里的所有内容，也不用担心，因为我们的目的是用一个非常完整的用户界面来结束课程，并提供LLM方面的所有功能。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "979cc4b6",
   "metadata": {
    "height": 30
   },
   "source": [
    "在格式聊天提示中，也就是我们之前使用的功能中，我们添加了一个新元素，那就是系统指令。因此，在开始用户助手对话之前，我们在系统顶部添加了一个指令。因此，基本上在发送给模型的每条信息的开头，都会有我们设置的系统信息。在这里，我们调用文本生成库的`generate_stream`函数。而 `generate_stream`函数的作用就是逐个生成响应标记。因此，在这个循环中，发生的事情就是按标记生成响应标记，将其添加到聊天记录中，然后将其返回给函数。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8d9ec80a-39ad-4f58-b79e-4f413c5074c0",
   "metadata": {
    "height": 30
   },
   "outputs": [],
   "source": [
    "# 关闭可能已经启动的任何先前的gradio实例\n",
    "gr.close_all()"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.13"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
